/*
 * Flashlib - QB9 common Flash library
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.qb9.flashlib.prototyping
{
	import com.qb9.flashlib.interfaces.IDisposable;
	import com.qb9.flashlib.math.QMath;
	import com.qb9.flashlib.utils.ClassUtil;
	
	import flash.display.DisplayObject;
	import flash.display.FrameLabel;
	import flash.display.MovieClip;
	import flash.events.Event;

	/**
	 * Provides a way to create programatic MovieClips.
	 * This is not something that can be done easily as all MovieClip's
	 * properties are read-only.
	 * 
	 * This MovieClip will work exactly as a real one, except that it won't clean
	 * itself up (empty) each time you switch frame.
	 * 
	 * If the inheriting class is dynamic then you can access its named children
	 * by accessing the MC's attribute by that name.
	 * 
	 * The labels array must be composed of FrameLabel instances
	 * You can use MovieUtil.makeLabels to generate an array easily
	 * 
	 * Check the example to understand this better.
	 */

	public class MockMovieClip extends MovieClip implements IDisposable
	{
		private var frame:int = 1;
		private var frames:int;
		private var labels:Array;

		public function MockMovieClip(totalFrames:int = int.MAX_VALUE, labels:Array = null)
		{
			this.labels = labels ? labels.sortOn('frame', Array.NUMERIC) : [ ];

			frames = totalFrames;
			
			setupDynamic();
			
			play();
		}

		// MovieClips' built-in getters

		public override function get currentFrame():int
		{
			return frame;
		}

		public override function get totalFrames():int
		{
			return frames;
		}

		public override function get currentLabel():String
		{
			var i:int = labels.length;
			while (i--)
				if (labels[i].frame <= frame)
					return labels[i].name;
			return null;
		}

		public override function get currentLabels():Array
		{
			return labels.concat();
		}

		// Frame Step Functions

		public override function prevFrame():void
		{
			gotoFrame(frame-1);
		}

		public override function nextFrame():void
		{
			gotoFrame(frame+1);
		}

		// Frame helpers

		protected function findLabel(name:Object) : FrameLabel
		{
			for each (var label:FrameLabel in labels)
				if (label.name === name)
					return label;
			return null;
		}
		
		protected function labelToFrame(name:Object) : int
		{
			var obj:FrameLabel = findLabel(name);
			return obj ? obj.frame : 1;
		}
		
		protected function goto(position:Object):void
		{
			// Label
			if (position is String)
				gotoFrame(labelToFrame(position));
			// Frame number
			else
				gotoFrame(int(position));
		}

		protected function gotoFrame(frame:int) : void
		{
			this.frame = QMath.clamp(frame, 1, frames);
			update();
		}
		
		// Called every time the current frame changes
		protected virtual function update() : void
		{
			// OVERRIDEME
		}

		// Stop

		public override function gotoAndStop(frame:Object, scene:String=null):void
		{
			goto(frame);
			stop();
		}

		public override function stop():void
		{
			removeEventListener(Event.ENTER_FRAME, step);
		}

		// Movie playing

		public override function gotoAndPlay(frame:Object, scene:String=null):void
		{
			goto(frame);
			play();
		}

		public override function play():void
		{
			addEventListener(Event.ENTER_FRAME, step);
		}

		protected function step(e:Event):void
		{
			if (frame === frames)
				gotoFrame(1);
			else
				nextFrame();
		}

		// Utility getters for mocking
		
		/** Returns the MovieClip's progress [0,1] */
		protected function get progress() : Number
		{
			return (frame-1) / (frames-1);
		}
		
		/** Returns the current label's progress or MovieClip's if not in a label */
		protected function get labelProgress() : Number
		{
			var label:FrameLabel = findLabel(currentLabel);
			if (!label)
				return NaN;
			
			var next:FrameLabel = labels[ labels.indexOf(label)+1 ] as FrameLabel;
			var total:int = next ? next.frame - 1 : frames;
			return (frame-label.frame) / (total-label.frame);
		}

		// Setup auto update of dynamic attributes to access children

		private function setupDynamic() : void
		{
			if (ClassUtil.isDynamic(this))
			{
				addEventListener(Event.ADDED, addElement);
				addEventListener(Event.REMOVED, removeElement);
			}
		}

		private function addElement(e:Event):void
		{
			var elem:DisplayObject = e.target as DisplayObject;
			if (elem.parent === this && elem.name)
				this[elem.name] = elem;
		}

		private function removeElement(e:Event):void
		{
			var elem:DisplayObject = e.target as DisplayObject;
			if (elem.parent === this && elem.name)
				delete this[elem.name];
		}
		
		public function dispose():void
		{
			removeEventListener(Event.ADDED, addElement);
			removeEventListener(Event.REMOVED, removeElement);
			
			stop();
			
			labels = null;
		}
	}
}
